package it.beppi.tristatetogglebutton_library;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.DragInfo;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Circle;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;

import ohos.app.Context;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import ohos.global.resource.ResourceManager;
import ohos.global.resource.solidxml.TypedAttribute;

import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import com.facebook.rebound.SimpleSpringListener;
import com.facebook.rebound.Spring;
import com.facebook.rebound.SpringConfig;
import com.facebook.rebound.SpringSystem;
import com.facebook.rebound.SpringUtil;

/**
 * @author ThinkPad
 * @author modified by BeppiMenozzi
 */
public class TriStateToggleButton extends ComponentContainer implements Component.DrawTask, Component.BindStateChangedListener,
        Component.EstimateSizeListener, ComponentContainer.ArrangeListener, Component.TouchEventListener, Component.DraggedListener {
    private Context mContext;

    private AttrSet mAttrSet;

    private String tbDefaultStatus;

    private String tbIsMidSelectable;

    private String tbMidColor;

    private String tbOffBorderColor;

    private String tbOffColor;

    private String tbOnColor;

    private String tbSpotColor;

    private boolean istbDefaultAnimatePresent;

    // Beppi: added a three state enumerator
    public enum ToggleStatus {
        on, mid, off
    }

    // Beppi: convert from the string values defined in the xml attributes to an usable value
    private ToggleStatus attrToStatus(String attr) {
        if (attr == null) return ToggleStatus.off;
        if (attr.equals("0")) return ToggleStatus.off;
        else if (attr.equals("1")) return ToggleStatus.mid;
        else return ToggleStatus.on;
    }

    // Beppi: static shortcuts for handling boolean values with 2-state. mid = false.
    public static boolean toggleStatusToBoolean(ToggleStatus toggleStatus) {
        if (toggleStatus == ToggleStatus.on) return true;
        else return false;
    }

    public static ToggleStatus booleanToToggleStatus(boolean toggleStatus) {
        if (toggleStatus) return ToggleStatus.on;
        else return ToggleStatus.off;
    }

    // Beppi: same with integers
    public static int toggleStatusToInt(ToggleStatus toggleStatus) {
        switch (toggleStatus) {
            case off:
                return 0;
            case mid:
                return 1;
            case on:
            default:
                return 2;
        }
    }

    public static ToggleStatus intToToggleStatus(int toggleIntValue) {
        if (toggleIntValue == 0) return ToggleStatus.off;
        else if (toggleIntValue == 1) return ToggleStatus.mid;
        else return ToggleStatus.on;
    }

    private SpringSystem springSystem;

    private Spring spring;

    private float radius;

    /**
     * 开启颜色
     * Turn on color
     * Beppi: Modified color to match material design
     * green 300
     */
    private int onColor = Color.getIntColor("#42bd41");

    /**
     * 关闭颜色
     * Turn off color
     * Beppi: Modified color to match material design
     * grey 400
     */
    private int offBorderColor = Color.getIntColor("#bdbdbd");

    /**
     * 灰色带颜色
     * Gray color
     */
    private int offColor = Color.getIntColor("#ffffff");

    /**
     * 手柄颜色
     * Handle color
     * Beppi: Added third mid color
     */
    private int midColor = Color.getIntColor("#ffca28");

    private int spotColor = Color.getIntColor("#ffffff");

    /**
     * 边框颜色
     * Border color
     */
    private int borderColor = offBorderColor;

    /**
     * 画笔
     * brush
     */
    private Paint paint;

    private Paint borderPaint;

    private Paint togglePaint;

    /**
     * 开关状态
     * switch status
     * Beppi: changed the type of toggleOn from boolean to ToggleStatus
     * Beppi: refactored the variable name from toggleOn to toggleStatus
     */
    private ToggleStatus toggleStatus = ToggleStatus.off;

    // Beppi: added previousToggleStatus to manage transitions correctly
    private ToggleStatus previousToggleStatus = ToggleStatus.off;

    /**
     * 边框大小
     * Border size
     */
    private int borderWidth = 2;

    /**
     * 垂直中心
     * Vertical center
     */
    private float centerY;

    /**
     * 按钮的开始和结束位置
     * The start and end positions of the button
     * Beppi: added midX position
     */
    private float startX, midX, endX;

    /**
     * 手柄X位置的最小和最大值
     */  // The minimum and maximum values for the X position of the handle
    // Beppi: added spotMidX
    private float spotMinX, spotMidX, spotMaxX;

    /**
     * 手柄大小
     * Handle size
     */
    private int spotSize;

    /**
     * 手柄X位置
     * Handle X position
     */
    private float spotX;

    /**
     * 关闭时内部灰色带高度
     * Off Internal gray band height
     */
    private float offLineWidth;

    /**
     *
     */
    private RectFloat rect = new RectFloat();

    /**
     * 默认使用动画
     * Animation is used by default
     */
    private boolean defaultAnimate = true;

    // Beppi: added midSelectable
    private boolean midSelectable = true;

    // Beppi: swipe management
    private int swipeSensitivityPixels = 200;

    private int swipeX = 0;

    /**
     * 是否默认处于打开状态
     * Whether it is on by default
     * Beppi: changed the type of isDefaultOn from boolean to ToggleStatus
     */
    private ToggleStatus defaultStatus = ToggleStatus.off;

    // Beppi: enabled && disabledColor
    private boolean enabled = true;

    private int disabledColor = Color.getIntColor("#bdbdbd");

    private boolean swipeing = false;

    private OnToggleChanged listener;

    public TriStateToggleButton(Context context) {
        super(context);
        this.mContext = context;
        addDrawTask(this);
        setArrangeListener(this);
        setup(mAttrSet);
        setTouchEventListener(this);
        setDraggedListener(1, this);
    }

    public TriStateToggleButton(Context context, AttrSet attrSet) {
        super(context, attrSet);
        this.mContext = context;
        this.mAttrSet = attrSet;
        addDrawTask(this);
        setArrangeListener(this);
        setup(mAttrSet);
        setTouchEventListener(this);
        setDraggedListener(1, this);
    }

    public TriStateToggleButton(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        this.mContext = context;
        setup(mAttrSet);
        addDrawTask(this);
        setArrangeListener(this);
        setTouchEventListener(this);
        setDraggedListener(1, this);
    }

    @Override
    public void onComponentBoundToWindow(Component component) {
        spring.addListener(springListener);
    }

    @Override
    public void onComponentUnboundFromWindow(Component component) {
        spring.removeListener(springListener);
    }

    public void setup(AttrSet attrs) {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);

        borderPaint = new Paint();
        borderPaint.setAntiAlias(true);
        borderPaint.setStyle(Paint.Style.FILL_STYLE);
        borderPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);

        togglePaint = new Paint();
        togglePaint.setAntiAlias(true);
        togglePaint.setStyle(Paint.Style.STROKE_STYLE);
        togglePaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);

        springSystem = SpringSystem.create(mContext);
        spring = springSystem.createSpring();
        spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(50, 7));

        this.setClickedListener(new ClickedListener() {
            @Override
            public void onClick(Component component) {
                toggle(defaultAnimate);
            }
        });

        boolean istbDefaultStatusPresent = attrs.getAttr("tbDefaultStatus").isPresent();
        if (istbDefaultStatusPresent) {
            tbDefaultStatus = attrs.getAttr("tbDefaultStatus").get().getStringValue().trim();
            if (tbDefaultStatus.equalsIgnoreCase("on")) {
                defaultStatus = ToggleStatus.on;
            } else if (tbDefaultStatus.equalsIgnoreCase("off")) {
                defaultStatus = ToggleStatus.off;
            } else if (tbDefaultStatus.equalsIgnoreCase("mid")) {
                defaultStatus = ToggleStatus.mid;
            }
        }

        boolean istbIsMidSelectablePresent = attrs.getAttr("tbIsMidSelectable").isPresent();
        if (istbIsMidSelectablePresent) {
            tbIsMidSelectable = attrs.getAttr("tbIsMidSelectable").get().getStringValue().trim();
            midSelectable = Boolean.parseBoolean(tbIsMidSelectable);
        }

        boolean istbMidColorPresent = attrs.getAttr("tbMidColor").isPresent();
        if (istbMidColorPresent) {
            tbMidColor = attrs.getAttr("tbMidColor").get().getStringValue().trim();
            midColor = Color.getIntColor(tbMidColor);
        }

        boolean istbOffBorderColorPresent = attrs.getAttr("tbOffBorderColor").isPresent();
        if (istbOffBorderColorPresent) {
            tbOffBorderColor = attrs.getAttr("tbOffBorderColor").get().getStringValue().trim();
            offBorderColor = Color.getIntColor(tbOffBorderColor);
        }

        boolean istbOffColorPresent = attrs.getAttr("tbOffColor").isPresent();
        if (istbOffColorPresent) {
            tbOffColor = attrs.getAttr("tbOffColor").get().getStringValue().trim();
            offColor = Color.getIntColor(tbOffColor);
        }

        boolean istbOnColorPresent = attrs.getAttr("tbOnColor").isPresent();
        if (istbOnColorPresent) {
            tbOnColor = attrs.getAttr("tbOnColor").get().getStringValue().trim();
            onColor = Color.getIntColor(tbOnColor);
        }

        boolean istbSpotColorPresent = attrs.getAttr("tbSpotColor").isPresent();
        if (istbSpotColorPresent) {
            tbSpotColor = attrs.getAttr("tbSpotColor").get().getStringValue().trim();
            spotColor = Color.getIntColor(tbSpotColor);
        }

        boolean istbBorderWidthPresent = attrs.getAttr("tbBorderWidth").isPresent();
        if (istbBorderWidthPresent) {
            borderWidth = Integer.parseInt(attrs.getAttr("tbBorderWidth").get().
                    getStringValue().trim().replaceAll("([a-z])", ""));
        }

        istbDefaultAnimatePresent = attrs.getAttr("tbAnimate").isPresent();
        if (istbDefaultAnimatePresent) {
            defaultAnimate = attrs.getAttr("tbAnimate").get().getBoolValue();
        }

        togglePaint.setStrokeWidth(borderWidth);
        enabled = true;
        swipeSensitivityPixels = 300;

        borderColor = offBorderColor;
        // Beppi: changed the usage of defaultStatus to match ToggleStatus type
        switch (defaultStatus) {
            case off:
                toggleOff();
                break;
            case mid:
                toggleMid();
                break;
            case on:
                toggleOn();
                break;
        }
    }

    public void toggle() {
        toggle(true);
    }

    // Beppi: modified to iterate on the 3 values instead of switching between two
    public void toggle(boolean animate) {
        if (midSelectable)
            switch (toggleStatus) {
                case off:
                    putValueInToggleStatus(ToggleStatus.mid);
                    break;
                case mid:
                    putValueInToggleStatus(ToggleStatus.on);
                    break;
                case on:
                    putValueInToggleStatus(ToggleStatus.off);
                    break;
            }
        else
            switch (toggleStatus) {
                case off:
                case mid:
                    putValueInToggleStatus(ToggleStatus.on);
                    break;
                case on:
                    putValueInToggleStatus(ToggleStatus.off);
                    break;
            }

        takeEffect(animate);

        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    public void toggleOn() {
        setToggleOn();
        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    public void toggleOff() {
        setToggleOff();
        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    // Beppi: added method to handle the mid value
    public void toggleMid() {
        setToggleMid();
        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    private void putValueInToggleStatus(ToggleStatus value) {
        if (!enabled) return;
        previousToggleStatus = toggleStatus;
        toggleStatus = value;
    }

    /**
     * 设置显示成打开样式，不会触发toggle事件
     * Setting the display to open style does not fire the toggle event
     */
    public void setToggleOn() {
        setToggleOn(true);
    }

    /**
     * setToggleOn
     *
     * @param animate asd
     */
    public void setToggleOn(boolean animate) {
        // Beppi: changed toggleStatus value from true to on
        putValueInToggleStatus(ToggleStatus.on);
        takeEffect(animate);
    }

    /**
     * 设置显示成关闭样式，不会触发toggle事件
     * Settings are shown as off styles, and the toggle event is not fired
     */
    public void setToggleOff() {
        setToggleOff(true);
    }

    public void setToggleOff(boolean animate) {
        // Beppi: changed toggleStatus value from false to off
        putValueInToggleStatus(ToggleStatus.off);
        takeEffect(animate);
    }

    // Beppi: added method for Mid value management
    public void setToggleMid(boolean animate) {
        putValueInToggleStatus(ToggleStatus.mid);
        takeEffect(animate);
    }

    public void setToggleMid() {
        setToggleMid(true);
    }

    //Beppi: added setToggleStatus() method, that imho was missing and needed
    public void setToggleStatus(ToggleStatus toggleStatus, boolean animate) {
        putValueInToggleStatus(toggleStatus);
        takeEffect(animate);
    }

    public void setToggleStatus(ToggleStatus toggleStatus) {
        setToggleStatus(toggleStatus, true);
    }

    public void setToggleStatus(boolean toggleStatus) {
        setToggleStatus(toggleStatus, true);
    }

    public void setToggleStatus(boolean toggleStatus, boolean animate) {
        if (toggleStatus) putValueInToggleStatus(ToggleStatus.on);
        else putValueInToggleStatus(ToggleStatus.off);
        takeEffect(animate);
    }

    public void setToggleStatus(int toggleIntValue) {
        setToggleStatus(toggleIntValue, true);
    }

    public void setToggleStatus(int toggleIntValue, boolean animate) {
        setToggleStatus(intToToggleStatus(toggleIntValue), animate);
    }

    // same as toggle, but after on does not rewind to off
    public void increaseValue(boolean animate) {
        switch (toggleStatus) {
            case off:
                if (midSelectable) putValueInToggleStatus(ToggleStatus.mid);
                else putValueInToggleStatus(ToggleStatus.on);
                break;
            case mid:
                putValueInToggleStatus(ToggleStatus.on);
                break;
            case on:
                break;
        }
        takeEffect(animate);
        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    public void increaseValue() {
        increaseValue(true);
    }

    public void decreaseValue(boolean animate) {
        switch (toggleStatus) {
            case on:
                if (midSelectable) putValueInToggleStatus(ToggleStatus.mid);
                else putValueInToggleStatus(ToggleStatus.off);
                break;
            case mid:
                putValueInToggleStatus(ToggleStatus.off);
                break;
            case off:
                break;
        }
        takeEffect(animate);
        if (listener != null) {
            listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
        }
    }

    public void decreaseValue() {
        decreaseValue(true);
    }

    private void takeEffect(boolean animate) {
        if (animate) {
            //这里没有调用spring，所以spring里的当前值没有变更，这里要设置一下，同步两边的当前值
            // There is no call spring, so the current value of the spring has not changed, here to set it, the current value on both sides of synchronization
            spring.setCurrentValue(toggleStatus == ToggleStatus.on ? 1 : toggleStatus == ToggleStatus.off ? 0 : 0.5);
            if (toggleStatus == ToggleStatus.on) calculateEffect(1);
            else if (toggleStatus == ToggleStatus.mid) calculateEffect(0.5);
            else calculateEffect(0);
        } else {
            if (toggleStatus == ToggleStatus.on)
                if (previousToggleStatus == ToggleStatus.off) {
                    if (midSelectable)
                        calculateEffect(0.5);
                    else calculateEffect(1);
                } else calculateEffect(1);
            else if (toggleStatus == ToggleStatus.mid) {
                if (midSelectable) calculateEffect(0.5);
                else
                    calculateEffect(1);
            } else calculateEffect(0);
            new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                @Override
                public void run() {
                    if (previousToggleStatus == ToggleStatus.on) calculateEffect(1);
                    else if (previousToggleStatus == ToggleStatus.mid) {
                        calculateEffect(1);
                        new EventHandler(EventRunner.getMainEventRunner()).postTask(new Runnable() {
                            @Override
                            public void run() {
                                calculateEffect(0.5);
                            }
                        }, 100);
                    } else calculateEffect(0);

                    toggleStatus = previousToggleStatus;
                }
            }, 100);
        }
    }

    @Override
    public boolean onEstimateSize(int widthEstimateSpec, int heightEstimateSpec) {
        final int widthMode = EstimateSpec.getMode(widthEstimateSpec);
        final int heightMode = EstimateSpec.getMode(heightEstimateSpec);
        int widthSize = EstimateSpec.getSize(widthEstimateSpec);
        int heightSize = EstimateSpec.getSize(heightEstimateSpec);

        ResourceManager r = getResourceManager();

        if (widthMode == 0 || widthSize == EstimateSpec.NOT_EXCEED) {
            widthSize = (int) TypedAttribute.computeTranslateRatio(r.getDeviceCapability());
            widthEstimateSpec = EstimateSpec.getSizeWithMode(widthSize, EstimateSpec.PRECISE);
        }

        if (heightMode == 0 || heightSize == EstimateSpec.NOT_EXCEED) {
            heightSize = (int) TypedAttribute.computeTranslateRatio(r.getDeviceCapability());
            heightEstimateSpec = EstimateSpec.getSizeWithMode(heightSize, EstimateSpec.PRECISE);
        }
        super.setEstimatedSize(widthEstimateSpec, heightEstimateSpec);
        return true;
    }


    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        final int width = getWidth();
        final int height = getHeight();
        radius = Math.min(width, height) * 0.5f;
        centerY = radius;
        startX = radius;
        endX = width - radius;
        spotMinX = startX + borderWidth;
        spotMaxX = endX - borderWidth;
        spotMidX = (startX + endX) / 2;
        spotSize = height - 4 * borderWidth;
        // Beppi: changed management of the position according to 3 states
        spotX = toggleStatus == ToggleStatus.on ? spotMaxX : toggleStatus == ToggleStatus.off ? spotMinX : spotMidX;
        offLineWidth = 0.0f;
        return true;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        int index = touchEvent.getIndex();
        MmiPoint mmiPoint = touchEvent.getPointerPosition(index);
        int x = (int) mmiPoint.getX();
        int action = touchEvent.getAction();
        if (action == TouchEvent.PRIMARY_POINT_DOWN) {
            swipeX = x;
            swipeing = false;
        } else if (action == TouchEvent.POINT_MOVE) {
            if (swipeSensitivityPixels == 0) return false;
            else if (x - swipeX > swipeSensitivityPixels) {
                swipeX = x;
                swipeing = true;
                increaseValue();
                return true;
            } else if (swipeX - x > swipeSensitivityPixels) {
                swipeX = x;
                swipeing = true;
                decreaseValue();
                return true;
            }
        } else if (action == TouchEvent.PRIMARY_POINT_UP) {
            if (!swipeing) toggle(defaultAnimate);    // here simple clicks are managed.
            return true;
        }
        return false;
    }

    SimpleSpringListener springListener = new SimpleSpringListener() {
        @Override
        public void onSpringUpdate(Spring spring) {
            final double value = spring.getCurrentValue();
            calculateEffect(value);
        }
    };

    private int clamp(int value, int low, int high) {
        return Math.min(Math.max(value, low), high);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (toggleStatus == ToggleStatus.off) {
            if (offColor == Color.getIntColor("#ffffff")) {
                paint.setStyle(Paint.Style.STROKE_STYLE);
                borderPaint.setStyle(Paint.Style.STROKE_STYLE);
                borderPaint.setStrokeWidth(borderWidth);
            }
        } else {
            paint.setStyle(Paint.Style.FILL_STYLE);
            borderPaint.setStyle(Paint.Style.FILL_STYLE);
        }

        rect.fuse(1.5f, 1.5f, component.getWidth() - 4, component.getHeight() - 4);

        if (offColor == Color.getIntColor("#ffffff")) {
            borderPaint.setColor(enabled ? (toggleStatus == ToggleStatus.mid ? new Color(midColor) :
                    ((toggleStatus == ToggleStatus.off ? new Color(offBorderColor) : new Color(onColor)))) :
                    (new Color(disabledColor)));
        } else {
            borderPaint.setColor(enabled ? (toggleStatus == ToggleStatus.mid ? new Color(midColor) :
                    ((toggleStatus == ToggleStatus.off ? new Color(offColor) : new Color(onColor)))) :
                    (new Color(disabledColor)));
        }


        if (!enabled) {
            borderPaint.setStyle(Paint.Style.FILLANDSTROKE_STYLE);
        }
        canvas.drawRoundRect(rect, radius, radius, borderPaint);

        Paint borderOffPaint = new Paint();
        borderOffPaint.setStyle(Paint.Style.STROKE_STYLE);
        borderOffPaint.setStrokeWidth(borderWidth);
        borderOffPaint.setColor(enabled ? (toggleStatus == ToggleStatus.mid ? new Color(midColor) :
                ((toggleStatus == ToggleStatus.off ? new Color(offBorderColor) : new Color(onColor)))) :
                (new Color(disabledColor)));
        canvas.drawRoundRect(rect, radius, radius, borderOffPaint);

        rect.fuse(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
        final float spotR = spotSize * 0.5f;
        rect.fuse(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
        if (spotColor != Color.getIntColor("#ffffff")) {
            paint.setStyle(Paint.Style.FILL_STYLE);
        }
        paint.setColor(enabled ? new Color(spotColor) : new Color(disabledColor));
        togglePaint.setColor((toggleStatus == ToggleStatus.off ? new Color(offBorderColor) : new Color(spotColor)));


        if (spotSize > 0) {
            Circle circle = new Circle(spotX, getPivotY(), (float) spotSize / 2);
            canvas.drawCircle(circle, paint);
            Circle borderCircle = new Circle(spotX, getPivotY(), (float) spotSize / 2);
            canvas.drawCircle(borderCircle, togglePaint);
        } else if (spotSize == 0) {
            Circle circle = new Circle(0, 0, 0);
            canvas.drawCircle(circle, paint);
            Circle borderCircle = new Circle(0, 0, 0);
            canvas.drawCircle(borderCircle, togglePaint);
        } else {
            Circle circle = new Circle(spotX, getPivotY(), spotSize);
            canvas.drawCircle(circle, paint);
            Circle borderCircle = new Circle(spotX, getPivotY(), spotSize);
            canvas.drawCircle(borderCircle, togglePaint);
        }
    }

    private void calculateEffect(final double value) {
        final float mapToggleX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
        spotX = mapToggleX;
        double min = 0, max = 0;
        int fromColor, toColor;
        if (previousToggleStatus == ToggleStatus.off && toggleStatus == ToggleStatus.mid) {
            toColor = offBorderColor;
            fromColor = midColor;
        } else if (previousToggleStatus == ToggleStatus.off && toggleStatus == ToggleStatus.on) {
            toColor = offBorderColor;
            fromColor = onColor;
        } else if (previousToggleStatus == ToggleStatus.mid && toggleStatus == ToggleStatus.on) {
            toColor = midColor;
            fromColor = onColor;
        } else if (previousToggleStatus == ToggleStatus.on && toggleStatus == ToggleStatus.off) {
            toColor = offBorderColor;
            fromColor = onColor;
        } else if (previousToggleStatus == ToggleStatus.on && toggleStatus == ToggleStatus.mid) {
            toColor = midColor;
            fromColor = onColor;
        } else {
            toColor = offBorderColor;
            fromColor = onColor;
        }

        if (previousToggleStatus == ToggleStatus.off) min = 0;
        else if (previousToggleStatus == ToggleStatus.mid) min = 0.5;
        else min = 1;
        if (toggleStatus == ToggleStatus.off) max = 0;
        else if (toggleStatus == ToggleStatus.mid) max = 0.5;
        else max = 1;

        if (min == max) {
            min = 0;
            max = 1;
        } else if (min > max) {
            double temp = min;
            min = max;
            max = temp;
        }

        offLineWidth = (float) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, 0, spotSize);

        RgbColor rgbColorFromB = new RgbColor(fromColor);
        final int fromB = rgbColorFromB.getBlue();
        RgbColor rgbColorFromR = new RgbColor(fromColor);
        final int fromR = rgbColorFromR.getRed();
        RgbColor rgbColorFromG = new RgbColor(fromColor);
        final int fromG = rgbColorFromG.getGreen();
        RgbColor rgbColorToB = new RgbColor(toColor);
        final int toB = rgbColorToB.getBlue();
        RgbColor rgbColorToR = new RgbColor(toColor);
        final int toR = rgbColorToR.getRed();
        RgbColor rgbColorToG = new RgbColor(toColor);
        final int toG = rgbColorToG.getGreen();
        int springB = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromB, toB);
        int springR = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromR, toR);
        int springG = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromG, toG);

        springB = clamp(springB, 0, 255);
        springR = clamp(springR, 0, 255);
        springG = clamp(springG, 0, 255);

        borderColor = Color.rgb(springR, springG, springB);

        invalidate();
    }

    /**
     * OnToggleChanged
     *
     * @author ThinkPad
     */
    public interface OnToggleChanged {
        /**
         * onToggle
         *
         * @param toggleStatus        ToggleStatus
         * @param booleanToggleStatus boolean
         * @param toggleIntValue      int
         */
        // Beppi: changed according to 3 states value
        void onToggle(ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue);
    }

    public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
        listener = onToggleChanged;
    }

    public boolean isAnimate() {
        return defaultAnimate;
    }

    public void setAnimate(boolean animate) {
        this.defaultAnimate = animate;
    }

    // Beppi: added all next methods, getters and setters

    public int getOnColor() {
        return onColor;
    }

    public void setOnColor(int onColor) {
        this.onColor = onColor;
        invalidate();
    }

    public int getOffBorderColor() {
        return offBorderColor;
    }

    public void setOffBorderColor(int offBorderColor) {
        this.offBorderColor = offBorderColor;
        invalidate();
    }

    public int getOffColor() {
        return offColor;
    }

    public void setOffColor(int offColor) {
        this.offColor = offColor;
        invalidate();
    }

    public int getMidColor() {
        return midColor;
    }

    public void setMidColor(int midColor) {
        this.midColor = midColor;
        invalidate();
    }

    public int getSpotColor() {
        return spotColor;
    }

    public void setSpotColor(int spotColor) {
        this.spotColor = spotColor;
        invalidate();
    }

    public int getBorderColor() {
        return borderColor;
    }

    public void setBorderColor(int borderColor) {
        this.borderColor = borderColor;
        invalidate();
    }

    public ToggleStatus getToggleStatus() {
        return toggleStatus;
    }

    public int getBorderWidth() {
        return borderWidth;
    }

    public void setBorderWidth(int borderWidth) {
        this.borderWidth = borderWidth;
        invalidate();
    }

    public int getSpotSize() {
        return spotSize;
    }

    public void setSpotSize(int spotSize) {
        this.spotSize = spotSize;
        invalidate();
    }

    public boolean isMidSelectable() {
        return midSelectable;
    }

    public void setMidSelectable(boolean midSelectable) {
        this.midSelectable = midSelectable;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @Override
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
        invalidate();
        super.setEnabled(enabled);
    }

    @Override
    public void onDragDown(Component component, DragInfo dragInfo) {
    }

    @Override
    public void onDragStart(Component component, DragInfo dragInfo) {
        swipeing = false;
    }

    @Override
    public void onDragUpdate(Component component, DragInfo dragInfo) {
        swipeing = false;
    }

    @Override
    public void onDragEnd(Component component, DragInfo dragInfo) {
        if (!swipeing) {
            toggle(defaultAnimate);
        }
    }

    @Override
    public void onDragCancel(Component component, DragInfo dragInfo) {
    }
}
